package cn.tedu.mall.seckill.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.restful.JsonPage;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.pojo.product.vo.SpuDetailStandardVO;
import cn.tedu.mall.pojo.product.vo.SpuStandardVO;
import cn.tedu.mall.pojo.seckill.model.SeckillSpu;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuDetailSimpleVO;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuVO;
import cn.tedu.mall.product.service.seckill.IForSeckillSpuService;
import cn.tedu.mall.seckill.mapper.SeckillSpuMapper;
import cn.tedu.mall.seckill.service.ISeckillSpuService;
import cn.tedu.mall.seckill.utils.RedisBloomUtils;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class SeckillSpuServiceImpl implements ISeckillSpuService {
    @Autowired
    private SeckillSpuMapper seckillSpuMapper;
    //seckill_spu没有spu的具体信息，需要利用product获取spu的信息
    @DubboReference
    private IForSeckillSpuService duuboSeckillSpuService;

    //SeckillCacheUtils类中没有定义spuDetail对应的key，所以我们自定义
    public static final String SECKILL_SPU_DETAIL_VO_PREFIX = "seckill:spu:detail:vo:";
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisBloomUtils redisBloomUtils;
    //spus列表查询是分页查询
    @Override
    public JsonPage<SeckillSpuVO> listSeckillSpus(Integer page, Integer pageSize) {
        //设置分页条件
        PageHelper.startPage(page,pageSize);
        //执行查询方法
        List<SeckillSpu> seckillSpus = seckillSpuMapper.findSeckillSpus();
        //声明返回值类型
        List<SeckillSpuVO> seckillSpuVOS = new ArrayList<>();
        //seckillSpus集合转成seckillSpuVOS
        for (SeckillSpu seckillSpu : seckillSpus) {
            //获取spuId，利用dubbo获取到spu的详细信息
            Long spuId = seckillSpu.getSpuId();
            SpuStandardVO spuStandardVO = duuboSeckillSpuService.getSpuById(spuId);
            //spuStandardVO对象中大部分数据和SeckillSpuVO是一样的，同名属性赋值
            SeckillSpuVO seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //赋值秒杀信息
            seckillSpuVO.setSeckillListPrice(seckillSpu.getListPrice());//秒杀价格
            seckillSpuVO.setStartTime(seckillSpu.getStartTime());//秒杀开始时间
            seckillSpuVO.setEndTime(seckillSpu.getEndTime());//秒杀结束时间
            seckillSpuVOS.add(seckillSpuVO);
        }
        //记得返回分页结果
        return JsonPage.restPage(new PageInfo<>(seckillSpuVOS));
    }

    //根据spuId查询spu信息
    @Override
    public SeckillSpuVO getSeckillSpu(Long spuId) {
        //先判断spuId是否在布隆过滤器中存在
        //先获取key值
        String bloomTodayKey = SeckillCacheUtils.getBloomFilterKey(LocalDate.now());
        log.info("当前批次的布隆过滤器的key为{}",bloomTodayKey);
        //如果在布隆过滤器中不存在，程序到处终止
        if(!redisBloomUtils.bfexists(bloomTodayKey,spuId+""))
            throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您访问的商品不存在(布隆过滤器测试)");
        //如果在布隆过滤器中存在，继续向后执行
        //声明一个返回值类型变量
        SeckillSpuVO seckillSpuVO = null;
        //先查询缓存
        //获取key
        String seckillSpuVOKey = SeckillCacheUtils.getSeckillSpuVOKey(spuId);
        if(redisTemplate.hasKey(seckillSpuVOKey)){
            seckillSpuVO = (SeckillSpuVO) redisTemplate.boundValueOps(seckillSpuVOKey).get();
        }else{
            //缓存中没有数据，查询数据库(seckill_spu、pms_spu)
            SeckillSpu seckillSpu = seckillSpuMapper.findSeckillSpuBySpuId(spuId);
            //判断seckillSpu是否为null，因为布隆过滤器误判时会进入到这里
            if(seckillSpu==null)
                throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您访问的商品不存在");
            //seckill不为null，继续查询pms_spu表
            SpuStandardVO spuStandardVO = duuboSeckillSpuService.getSpuById(spuId);
            //将spuStandardVO转成seckillSpuVo
            seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //将秒杀信息赋值到seckillSpuVO对象中
            seckillSpuVO.setSeckillListPrice(seckillSpu.getListPrice());//秒杀价格
            seckillSpuVO.setStartTime(seckillSpu.getStartTime());//秒杀开始时间
            seckillSpuVO.setEndTime(seckillSpu.getEndTime());//秒杀结束时间
            //将seckillSpuVO缓存到redis中，以备后续用户使用
            redisTemplate.boundValueOps(seckillSpuVOKey).set(seckillSpuVO
                    ,24*60*60+RandomUtils.nextInt(100),TimeUnit.SECONDS);
        }
        //判断当前时间是否在秒杀时间范围内
        //不再查询数据库，seckillSpuVO对象中有秒杀开始和结束时间
        LocalDateTime startTime = seckillSpuVO.getStartTime();
        LocalDateTime endTime = seckillSpuVO.getEndTime();
        LocalDateTime nowTime = LocalDateTime.now();
        //nowTime小于endTime，并且大于startTime
        //使用Duration时间对象，可以用于计算时间差
        //时间差为负值，返回的信息是negative
        //只需要判断 startTime-nowTime是negative，nowTime-endTime是negative，就说明在秒杀时间范围内
        Duration afterTime = Duration.between(nowTime, startTime);
        Duration beforeTime = Duration.between(endTime, nowTime);
        //判断afterTime和beforeTime是否都是negative，是，就表示在时间范围内
        if(afterTime.isNegative()&&beforeTime.isNegative()){
            //在秒杀使劲按范围内，可以进行购买
            //获取随机码，赋值到url属性中
            //我们业务设计购买路径是"/seckill/{randCode}"
            //获取随机码
            String randCodeKey = SeckillCacheUtils.getRandCodeKey(spuId);
            int randCode = (int)redisTemplate.boundValueOps(randCodeKey).get();
            seckillSpuVO.setUrl("/seckill/"+randCode);
        }
        //seckillSpuVO对象中包含了url以及随机码，这个随机码会发送给前端保存
        //前端利用这个url才能发起购买的请求
        return seckillSpuVO;
    }

    //根据spuId查询spu的detail
    @Override
    public SeckillSpuDetailSimpleVO getSeckillSpuDetail(Long spuId) {
        //获取key
        String seckillDetailKey = SECKILL_SPU_DETAIL_VO_PREFIX+spuId;
        //声明一个SeckillSpuDetailSimpleVO类型对象
        SeckillSpuDetailSimpleVO seckillSpuDetailSimpleVO = null;
        //判断缓存中是否存在该商品信息
        if(redisTemplate.hasKey(seckillDetailKey)){
            seckillSpuDetailSimpleVO = (SeckillSpuDetailSimpleVO)
                    redisTemplate.boundValueOps(seckillDetailKey).get();
        }else{
            //缓存中没有数据，查询数据库，放到缓存中，返回信息
            SpuDetailStandardVO spuDetailStandardVO = duuboSeckillSpuService.getSpuDetailById(spuId);
            seckillSpuDetailSimpleVO = new SeckillSpuDetailSimpleVO();
            BeanUtils.copyProperties(spuDetailStandardVO,seckillSpuDetailSimpleVO);
            redisTemplate.boundValueOps(seckillDetailKey).set(seckillSpuDetailSimpleVO,
                    24*60*60+ RandomUtils.nextInt(100), TimeUnit.SECONDS);
        }
        return seckillSpuDetailSimpleVO;
    }
}
